/*HEADER**********************************************************************
*
* Copyright 2008 Freescale Semiconductor, Inc.
* Copyright 1989-2008 ARC International
*
* This software is owned or controlled by Freescale Semiconductor.
* Use of this software is governed by the Freescale MQX RTOS License
* distributed with this Material.
* See the MQX_RTOS_LICENSE file distributed for more details.
*
* Brief License Summary:
* This software is provided in source form for you to use free of charge,
* but it is not open source software. You are allowed to use this software
* but you cannot redistribute it or derivative works of it in source form.
* The software may be used only in connection with a product containing
* a Freescale microprocessor, microcontroller, or digital signal processor.
* See license agreement file for full license terms including other
* restrictions.
*****************************************************************************
*
* Comments:
*
*   This file Contains the implementation of class driver for CDC devices.
*
*
*END************************************************************************/

#include "usb_host_serial.h"

#if MQX_USE_IO_OLD //TODO: make CDC independent on MQX_FILE_PTR (old IO subsystem)

#define _HOST_DEBUG_

static CLASS_CALL_STRUCT_PTR           serial_anchor = NULL;

static void usb_class_serial_deinit(_usb_class_intf_handle);

/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_class_cdc_data_init
* Returned Value : None
* Comments       :
*     This function is called by common class to initialize the class driver
*     for AbstractClassControl. It is called in response to a select
*     interface call by application.
*
*END*--------------------------------------------------------------------*/

void usb_class_serial_init
   (
      /* [IN]  structure with USB pipe information on the interface */
      PIPE_BUNDLE_STRUCT_PTR      pbs_ptr,

      /* [IN] acm call struct pointer */
      CLASS_CALL_STRUCT_PTR       ccs_ptr
   )
{ /* Body */
    USB_SERIAL_CLASS_INTF_STRUCT_PTR if_ptr = (USB_SERIAL_CLASS_INTF_STRUCT_PTR)ccs_ptr->class_intf_handle;
    USB_STATUS                     status;
 
    #ifdef _HOST_DEBUG_
        DEBUG_LOG_TRACE("usb_class_serial_init");
    #endif

	/* Set some sane defaults */
    if_ptr->rts_state   = 1;
    if_ptr->dtr_state   = 1;
    
    /* Make sure the device is still attached */
    USB_lock();  //not needed as init is called from "already locked" function
    status = usb_host_class_intf_init(pbs_ptr, if_ptr, &serial_anchor, usb_class_serial_deinit);
    if (status == USB_OK) {
        /*
        ** We generate a code_key based on the attached device. This is used to
        ** verify that the device has not been detached and replaced with another.
        */
        ccs_ptr->code_key = 0;
        ccs_ptr->code_key = usb_host_class_intf_validate(ccs_ptr);
#if 1
        if_ptr->SERIAL_G.IFNUM = ((INTERFACE_DESCRIPTOR_PTR)(if_ptr->SERIAL_G.G.intf_handle))->bInterfaceNumber;

        printf("IFNUM=%d\r\n", if_ptr->SERIAL_G.IFNUM);

        if_ptr->in_pipe     = usb_hostdev_get_pipe_handle(pbs_ptr, USB_BULK_PIPE, USB_RECV);
        if_ptr->out_pipe    = usb_hostdev_get_pipe_handle(pbs_ptr, USB_BULK_PIPE, USB_SEND);
        if_ptr->int_in_pipe = usb_hostdev_get_pipe_handle(pbs_ptr, USB_INTERRUPT_PIPE, USB_RECV);        

        if (if_ptr->in_pipe == NULL)
        {
            status = USBERR_OPEN_PIPE_FAILED;
#ifdef _HOST_DEBUG_
            DEBUG_LOG_TRACE("in_pipe=NULL");
#endif
        }
        if (if_ptr->out_pipe == NULL)
        {
            status = USBERR_OPEN_PIPE_FAILED;
#ifdef _HOST_DEBUG_
            DEBUG_LOG_TRACE("out_pipe=NULL");
#endif
        }
        
        if (if_ptr->in_pipe)
        {
            /* Don't use host - predefined constant for NAK_COUNT...
            ** NOTE!!!
            ** This hack is not very clean. We need to maximize number of retries to minimize the time of
            ** transaction (minimize task's time while waiting for 1 transaction to be done (with or without data))
            ** The time depends on user expecatation of the read() latency, on the delay between 2 NAKs and on number
            ** of NAKs to be performed.
            ** The workaround is to limit amount of retries for the pipe maximally to 3.
            ** Number 3 is hard-coded here for now.
            */
            if (if_ptr->in_pipe->NAK_COUNT > 3)
                if_ptr->in_pipe->NAK_COUNT = 3; /* don't use host - predefined constant */

            /* initialize buffer */
            /* size of buffer equals to the size of endpoint data size */
            if_ptr->RX_BUFFER_SIZE = if_ptr->in_pipe->MAX_PACKET_SIZE;
            if (NULL == (if_ptr->RX_BUFFER = USB_mem_alloc_zero(if_ptr->RX_BUFFER_SIZE))) 
            {
                printf("RX_BUFFER alloc  error!\n");
                status = USBERR_ALLOC;
            }
            else 
            {
                /* initialize members */
                if_ptr->RX_BUFFER_APP = if_ptr->RX_BUFFER_DRV = if_ptr->RX_BUFFER;
            }
            printf("\nin_pipe endpointnum=%d\n", if_ptr->in_pipe->ENDPOINT_NUMBER);
        }
       
        if (if_ptr->out_pipe)
        {
            /* The same as for OUT pipe applies here */
            if (if_ptr->out_pipe->NAK_COUNT > 3)
               if_ptr->out_pipe->NAK_COUNT = 3; /* don't use host - predefined constant */
        
            printf("\nout_pipe endpointnum=%d\n", if_ptr->out_pipe->ENDPOINT_NUMBER);
        }
        if (if_ptr->int_in_pipe)
        {
            if_ptr->INT_IN_BUFFER = USB_mem_alloc_zero(if_ptr->int_in_pipe->MAX_PACKET_SIZE);
            
            if (NULL == if_ptr->INT_IN_BUFFER)
            {
                printf("INT_IN_BUFFER alloc error!\n");
                status = USBERR_ALLOC;
            }
            printf("\nint_in_pipe endpointnum=%d, MAX_PACKET_SIZE=%d\n", 
            if_ptr->int_in_pipe->ENDPOINT_NUMBER,
            if_ptr->int_in_pipe->MAX_PACKET_SIZE);
        }
#endif
    } /* Endif */

    /* Signal that an error has occured by setting the "code_key" */
    if (status) {
        ccs_ptr->code_key = 0;
    } /* Endif */
 
    USB_unlock();
 
    #ifdef _HOST_DEBUG_
    if (!status) {
         DEBUG_LOG_TRACE("usb_class_serial_init_ipipe, SUCCESSFUL");
    }
    else {
         DEBUG_LOG_TRACE("usb_class_serial_init_ipipe, FAILED");
    }
    #endif
   
} /* Endbody */


/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_class_cdc_data_use_lwevent
* Returned Value : None
* Comments       :
*     This function is injector of events that are used in the class but
*     the destruction are allowed only in task context.
*
*END*--------------------------------------------------------------------*/

USB_STATUS usb_class_serial_use_lwevent
   (
      /* [IN] acm call struct pointer */
      CLASS_CALL_STRUCT_PTR       ccs_ptr,

      /* [IN] acm event */
      LWEVENT_STRUCT_PTR          serial_event
   )
{
    USB_SERIAL_CLASS_INTF_STRUCT_PTR if_ptr = (USB_SERIAL_CLASS_INTF_STRUCT_PTR)ccs_ptr->class_intf_handle;

    if (serial_event == NULL) {
        return USBERR_INIT_DATA;
    }

    if_ptr->serial_event = serial_event;

    /* prepare events to be auto or manual */
    _lwevent_set_auto_clear(if_ptr->serial_event, USB_SERIAL_READ_PIPE_FREE | USB_SERIAL_SEND_PIPE_FREE);
    /* pre-set events */
    _lwevent_clear(if_ptr->serial_event, 0xFFFFFFFF);
    _lwevent_set(if_ptr->serial_event, USB_SERIAL_READ_PIPE_FREE | USB_SERIAL_SEND_PIPE_FREE);

    return USB_OK;
}

/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_class_cdc_data_deinit
* Returned Value : None
* Comments       :
*     This function is destructor for device instance called after detach
*
*END*--------------------------------------------------------------------*/

static void usb_class_serial_deinit
   (
      /* [IN] data call struct pointer */
      _usb_class_intf_handle  if_handle
   )
{
    USB_SERIAL_CLASS_INTF_STRUCT_PTR if_ptr = (USB_SERIAL_CLASS_INTF_STRUCT_PTR)if_handle;

    if ((if_ptr->in_pipe != NULL) && (if_ptr->serial_event != NULL))
        _lwevent_set(if_ptr->serial_event, USB_SERIAL_DETACH); /* mark we are not using input pipe */

    if (if_ptr->RX_BUFFER != NULL) {
        USB_mem_free(if_ptr->RX_BUFFER);
    }

    if (if_ptr->INT_IN_BUFFER != NULL) {
        USB_mem_free(if_ptr->INT_IN_BUFFER);
    }
    /* destroying lwevent is up to application */
} /* Endbody */

/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_class_cdc_register_interface
* Returned Value :
* Comments       :
*     This function is used to register interface in the chain.
*     It must be run with interrupts disabled to have interfaces validated.
*END*--------------------------------------------------------------------*/

static void usb_class_serial_register_interface
   (
      /* [IN] pointer to registered interface */
      CLASS_CALL_STRUCT_PTR  class_ptr,
      
      CLASS_CALL_STRUCT_PTR  *anchor_ptr
   )
{
    CLASS_CALL_STRUCT_PTR     interface_parser;

    if (*anchor_ptr == NULL) {
        *anchor_ptr = class_ptr;
        (*anchor_ptr)->next = NULL;
    }
    else {
        interface_parser = *anchor_ptr;
        /* add new class to the list */
        while (interface_parser->next != NULL)
            interface_parser = interface_parser->next;
        interface_parser->next = class_ptr;
        class_ptr->next = NULL;
    }
}

/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_class_cdc_unregister_interface
* Returned Value :
* Comments       :
*     This function is used to unregister interface from the chain.
*     It must be run with interrupts disabled to have interfaces validated.
*END*--------------------------------------------------------------------*/

static void usb_class_serial_unregister_interface
   (
      /* [IN] pointer to control interface */
      CLASS_CALL_STRUCT_PTR       class_ptr,
      
      CLASS_CALL_STRUCT_PTR  *anchor_ptr
   )
{
    CLASS_CALL_STRUCT_PTR     interface_parser;
   
    USB_lock();
    if (*anchor_ptr != NULL) {
        if (*anchor_ptr == class_ptr)
            *anchor_ptr = class_ptr->next;
        else {
            /* remove control class from the list */
            for (interface_parser = *anchor_ptr; interface_parser->next != NULL; interface_parser = interface_parser->next) {
                if (interface_parser->next == class_ptr)  {
                    interface_parser->next = class_ptr->next;
                    break;
                }
            }
        }
    }
    USB_unlock();
}

/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_class_serial_get_interface
* Returned Value : control interface instance
* Comments       :
*     This function is used to find registered control interface in the chain.
*END*--------------------------------------------------------------------*/

CLASS_CALL_STRUCT_PTR usb_class_serial_get_interface
   (
      /* [IN] pointer to interface handle */
      void             *intf_handle
   )
{
    CLASS_CALL_STRUCT_PTR   serial_parser;

    #ifdef _HOST_DEBUG_
        DEBUG_LOG_TRACE("usb_class_cdc_get_ctrl_interface");
    #endif

    USB_lock();
    /* find this acm interface in the list with interface descriptor */
    for (serial_parser = serial_anchor; serial_parser != NULL; serial_parser = serial_parser->next)
        if (usb_host_class_intf_validate(serial_parser))
            if (((USB_SERIAL_CLASS_INTF_STRUCT_PTR) serial_parser->class_intf_handle)->SERIAL_G.G.intf_handle == intf_handle)
                break;
    USB_unlock();
       
    #ifdef _HOST_DEBUG_
    if (serial_parser != NULL) {
        DEBUG_LOG_TRACE("usb_class_serial_get_interface, SUCCESSFULL");
    }
    else {
        DEBUG_LOG_TRACE("usb_class_serial_get_interface, FAILED");
    }
    #endif

    return serial_parser;
}

/*FUNCTION*----------------------------------------------------------------
*
* Function Name  : usb_class_serial_set_interface
* Returned Value : USB_OK
* Comments       :
*     Data interface (specified by ccs_ptr) will be bound to appropriate
*     control interface.
*     Must be run in locked state and validated USB device.
*END*--------------------------------------------------------------------*/

USB_STATUS usb_class_serial_set_interface
   (
      CLASS_CALL_STRUCT_PTR            ccs_ptr,
      
      INTERFACE_DESCRIPTOR_PTR         if_desc
   )
{
    #ifdef _HOST_DEBUG_
        DEBUG_LOG_TRACE("usb_class_cdc_bind_acm_interface");
    #endif
    
    USB_lock();
    usb_class_serial_register_interface(ccs_ptr, &serial_anchor);
    USB_unlock();
   
    #ifdef _HOST_DEBUG_
        DEBUG_LOG_TRACE("usb_class_cdc_bind_acm_interface, SUCCESSFULL");
    #endif

    return USB_OK;
}


#endif //MQX_USE_IO_OLD

/* EOF */
